06.3 精通自定义 View 之 Paint 基本使用——常用函数

返回自定义 View 目录

6.1.1 基本函数设置

我们先来看一下 paint 中基本设置的函数都有哪些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 重置画笔
reset()
// 给画笔设置颜色值
setColor(int color)
// 设置颜色,利用 ARGB 分开设置
setARGB(int a, int r, int g, int b)
// 设置画笔透明度
setAlpha(int a)
// 设置画笔样式,取值有:Paint.Style.FILL、STROKE、FILL_AND_STROKE
setStyle(Paint.Style style)
// 设置画笔宽度
setStrokeWidth(float width)
// 设置画笔是否抗锯齿
setAntiAlias(boolean aa)
// 设置线冒样式,取值有Cap.ROUND(圆形线冒)、
// Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
setStrokeCap(Paint.Cap cap)
// 设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、
// Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
setStrokeJoin(Paint.Join join)
// 设置笔画的倾斜度,区别不明显
setStrokeMiter(float miter)
// 设置路径样式。取值类型是所有派生自 PathEffect 的子类:
// ComposePathEffect、CornerPathEffect、DashPathEffect、
// DiscretePathEffect、PathDashPathEffect、SumPathEffect
setPathEffect(PathEffect effect)

1. setStrokeCap(Paint.Cap cap)

设置线帽样式,取值有 Cap.ROUND (圆形线帽)、Cap.SQUARE (方形线帽)、Paint.Cap.BUTT (无线帽)

红线左侧多出来的区域就是线帽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TestView extends View {
private Paint mPaint;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(80);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 无线帽
mPaint.setStrokeCap(Paint.Cap.BUTT);
canvas.drawLine(100, 200, 400, 200, mPaint);
// 方形线帽
mPaint.setStrokeCap(Paint.Cap.SQUARE);
canvas.drawLine(100, 400, 400, 400, mPaint);
// 圆形线帽
mPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawLine(100, 600, 400, 600, mPaint);
// 画辅助线
mPaint.reset();
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.RED);
canvas.drawLine(100, 50, 100, 750, mPaint);
}
}

2. setStrokeJoin(Paint.Join join)

参数取值有:Join.MITER (结合处为锐角)、Join.Round (结合处为圆弧)、Join.BEVEL(结合处为直线)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TestView extends View {
private Paint mPaint;
private Path mPath;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(40);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 结合处为锐角 MITER
mPath.moveTo(100, 100);
mPath.lineTo(250, 100);
mPath.lineTo(100, 250);
mPaint.setStrokeJoin(Paint.Join.MITER);
canvas.drawPath(mPath, mPaint);
// 结合处为圆弧 ROUND
mPath.moveTo(300, 100);
mPath.lineTo(450, 100);
mPath.lineTo(300, 250);
mPaint.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(mPath, mPaint);
// 结合处为直线 BEVEL
mPath.moveTo(500, 100);
mPath.lineTo(650, 100);
mPath.lineTo(500, 250);
mPaint.setStrokeJoin(Paint.Join.BEVEL);
canvas.drawPath(mPath, mPaint);
}
}

3. setPathEffect(PathEffect effect)

设置路径样式。取值类型是所有派生自 PathEffect 的子类:ComposePathEffect、CornerPathEffect、DashPathEffect、DiscretePathEffect、PathDashPathEffect、SumPathEffect。

1)CornerPathEffect
它的作用就是将原来 Path 生硬的直线拐角,变成圆形拐角。

1
public CornerPathEffect(float radius)

参数 radius:即当前连接两条直线所使用的圆的半径。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class TestView extends View {
private Paint mPaint;
private Path mPath;
private CornerPathEffect effect100, effect200;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPath = new Path();
effect100 = new CornerPathEffect(100);
effect200 = new CornerPathEffect(200);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.moveTo(100,600);
mPath.lineTo(400,100);
mPath.lineTo(700,900);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(Color.RED);
mPaint.setPathEffect(effect100);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(Color.BLUE);
mPaint.setPathEffect(effect200);
canvas.drawPath(mPath, mPaint);
}
}

2)DashPathEffect 虚线效果

1
public DashPathEffect(float intervals[], float phase)

phase:开始绘制的偏移值。
intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由 intervals[] 中这些基本线段循环组成的。比如,我们定义 new float[] {20,10};那这个虚线段就是由两段线段组成的,第一个可见的线段长为 20,每二个线段不可见,长度为 10。

对于 intervals[] 数组的有两个限定:

  • 长度必须大于等于 2;因为必须有一个实线段和一个空线段来组成虚线。
  • 个数必须为偶数,如果是基数,最后一个数字将被忽略;这个很好理解,因为一组虚线的组成必然是一个实线和一个空线成对组成的。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TestView extends View {
private Paint mPaint;
private Path mPath;
private DashPathEffect effect1, effect2;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPath = new Path();
effect1 = new DashPathEffect(new float[]{20,10,100,100},0);
effect2 = new DashPathEffect(new float[]{20,10,50,100},15);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.moveTo(100,600);
mPath.lineTo(400,100);
mPath.lineTo(700,900);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(Color.RED);
mPaint.setPathEffect(effect1);
canvas.translate(0, 100);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(Color.BLUE);
mPaint.setPathEffect(effect2);
canvas.translate(0, 100);
canvas.drawPath(mPath, mPaint);
}
}

3)DiscretePathEffect 离散路径效果
DiscretePathEffect 就是将原来路径分隔成定长的线段,然后将每条线段随机偏移一段位置,我们可以用它来模拟一种类似生锈铁丝的效果。

1
public DiscretePathEffect(float segmentLength, float deviation)

  • 参数 segmentLength:表示将原来的路径切成多长的线段。如果值为 2,那么这个路径就会被切成一段段由长度为 2 的小线段。所以这个值越小,所切成的小线段越多;这个值越大,所切成的小线段越少。
  • 参数 deviation:表示被切成的每个小线段的可偏移距离。值越大,就表示每个线段的可偏移距离就越大,就显得越凌乱,值越小,每个线段的可偏移原位置的距离就越小。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class TestView extends View {
private Paint mPaint;
private Path mPath;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = getPaint();
mPath = getPath();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 第一条原生 Path
canvas.drawPath(mPath, mPaint);
// 第二条 Path
canvas.translate(0,200);
mPaint.setPathEffect(new DiscretePathEffect(2,5));
canvas.drawPath(mPath, mPaint);
// 第三条 Path
canvas.translate(0,200);
mPaint.setPathEffect(new DiscretePathEffect(6,5));
canvas.drawPath(mPath, mPaint);
// 第四条 Path
canvas.translate(0,200);
mPaint.setPathEffect(new DiscretePathEffect(6,15));
canvas.drawPath(mPath, mPaint);
}
private Path getPath(){
Path path = new Path();
// 定义路径的起点
path.moveTo(0, 0);
// 定义路径的各个点
for (int i = 0; i <= 40; i++) {
path.lineTo(i*35, (float) (Math.random() * 150));
}
return path;
}
private Paint getPaint(){
Paint paint = new Paint();
paint.setStrokeWidth(4);
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
return paint;
}
}

4)PathDashPathEffect 印章路径效果
它的作用就是用另一个路径图案做为印章,沿着指定路径一个个盖上去。

1
public PathDashPathEffect(Path shape, float advance, float phase,Style style)

  • Path shape:表示印章路径,比如我们下面示例中的三角形加右上角一个点。
  • float advance:表示两个印章路径间的距离,印章间距离越大,间距就越大。
  • float phase:路径绘制偏移距离,与上面 DashPathEffect 中的 float phase 参数意义相同。
  • Style style:表示在遇到转角时,如何操作印章以使转角平滑过渡,取值有:Style.ROTATE,Style.MORPH,Style.TRANSLATE;Style.ROTATE 表示通过旋转印章来过渡转角;Style.MORPH 表示通过变形印章来过渡转角;Style.TRANSLATE 表示通过位移来过渡转角。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class TestView extends View {
private Paint mPaint;
private Path mPath;
private PathDashPathEffect mEffectMORPH, mEffectROTATE, mEffectTRANSLATE;
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = getPaint();
mPath = getPath();
// 构建印章路径
Path stampPath = getStampPath();
stampPath.addCircle(0,0,3, Path.Direction.CCW);
mEffectMORPH = new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.MORPH);
mEffectROTATE = new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.ROTATE);
mEffectTRANSLATE = new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.TRANSLATE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 原始线
canvas.drawPath(mPath, mPaint);
// 使用印章路径效果 MORPH
canvas.translate(0,200);
mPaint.setPathEffect(mEffectMORPH);
canvas.drawPath(mPath, mPaint);
// 使用印章路径效果 ROTATE
canvas.translate(0,200);
mPaint.setPathEffect(mEffectROTATE);
canvas.drawPath(mPath, mPaint);
// 使用印章路径效果 TRANSLATE
canvas.translate(0,200);
mPaint.setPathEffect(mEffectTRANSLATE);
canvas.drawPath(mPath, mPaint);
}
private Path getPath(){
Path path = new Path();
// 定义路径的起点
path.moveTo(0, 0);
// 定义路径的各个点
for (int i = 0; i <= 40; i++) {
path.lineTo(i*35, (float) (Math.random() * 150));
}
return path;
}
private Path getStampPath(){
Path path = new Path();
path.moveTo(0,20);
path.lineTo(10,0);
path.lineTo(20,20);
path.close();
path.addCircle(0,0,3, Path.Direction.CCW);
return path;
}
private Paint getPaint(){
Paint paint = new Paint();
paint.setStrokeWidth(4);
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
return paint;
}
}

5)ComposePathEffect & SumPathEffect
这两个都是用来合并两个特效的。但它们之间是有区别的:

1
2
public ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
public SumPathEffect(PathEffect first, PathEffect second)

ComposePathEffect 合并两个特效是有先后顺序的,它会先将第二个参数的 PathEffect innerpe 的特效作用于路径上,然后再在此加了特效的路径上作用第一个特效。

而 SumPathEffect 是分别对原始路径分别作用第一个特效和第二个特效。然后再将这两条路径合并,做为最终结果。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 画原始路径
Paint paint = getPaint();
Path path = getPath();
canvas.drawPath(path,paint);
// 仅应用圆角特效的路径
canvas.translate(0,200);
CornerPathEffect cornerPathEffect = new CornerPathEffect(100);
paint.setPathEffect(cornerPathEffect);
canvas.drawPath(path,paint);
// 仅应用虚线特效的路径
canvas.translate(0,200);
DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2,5,10,10},0);
paint.setPathEffect(dashPathEffect);
canvas.drawPath(path,paint);
// 利用 ComposePathEffect 先应用圆角特效,再应用虚线特效
canvas.translate(0,200);
ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect,cornerPathEffect);
paint.setPathEffect(composePathEffect);
canvas.drawPath(path,paint);
// 利用 SumPathEffect,分别将圆角特效应用于原始路径,然后将生成的两条特效路径合并
canvas.translate(0,200);
paint.setStyle(Paint.Style.STROKE);
SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect,dashPathEffect);
paint.setPathEffect(sumPathEffect);
canvas.drawPath(path,paint);

6.1.2 字体相关函数

setTextSize(float textSize)
设置文字大小

setFakeBoldText(boolean fakeBoldText)
设置是否为粗体文字

setStrikeThruText(boolean strikeThruText)
设置带有删除线效果

setUnderlineText(boolean underlineText)
设置下划线

setTextAlign(Paint.Align align)
设置开始绘图点位置

setTextScaleX(float scaleX)
水平拉伸设置

setTextSkewX(float skewX)
设置字体水平倾斜度,普通斜体字是-0.25,可见往右斜

setTypeface(Typeface typeface)
字体样式

setLinearText(boolean linearText)
设置是否打开线性文本标识;由于文本想要快速绘制出来,必然是需要提前缓存在显存中的,一般而言每个文字需要一个字节的大小来存储它(当然具体需要多少字节与编码方式有关),那如果是长篇文章,可见所需的大小可想而知。我们可以通过 setLinearText (true) 告诉 Android 我们不需要这样的文本缓存。但如果我们不用文本缓存,虽然能够省去一些内存空间,但这是以显示速度为代价的。

由于这个是 API 1 的函数,由于当时的 android 手机的内存大小还是很小的,所以尽量减少内存使用是每个应用的头等大事,在当时的的环境下这个函数还是很有用的。

但在今天,内存动不动就是 4G 以上了,文本缓存的所占的那点内存就微不足道了,没有哪个 APP 会牺牲性能来减少这点这内存占用了,所以这个函数基本没用了。

setSubpixelText(boolean subpixelText)
表示是否打开亚像素设置来绘制文本。亚像素的概念比较难理解,首先,我们都知道像素,比如一个 android 手机的分辨率是 1280720,那就是指它的屏幕在垂直方向有 1280 个像素点,水平方向上有 720 个像素点。我们知道每个像素点都是一个独立显示一个颜色的个体。所以如果一副图片,在一个屏幕上用了 300100 个相素点,而在另一个屏幕上却用了 450*150 个像素来显示。那么,请问在哪个屏幕上这张图片显示的更清晰?当然是第二个屏幕,因为它使用的像素点更多,所显示的细节更精细。

那么问题来了,android 设置在出厂时,设定的像素显示都是固定的几个范围:320*480,480*800,720*1280,1080*1920 等等;那么如何在同样的分辨率的显示器中增强显示清晰度呢?

亚像素的概念就油然而生了,亚像素就是把两个相邻的两个像素之间的距离再细分,再插入一些像素,这些通过程序加入的像素就是亚像素。在两个像素间插入的像素个数是通过程序计算出来的,一般是插入两个、三个或四个。

所以打开亚像素显示,是可以在增强文本显示清晰度的,但由于插入亚像素是通过程序计算而来的,所以会耗费一定的计算机性能。注意:亚像素是通过程序计算出来模拟插入的,在没有改变硬件构造的情况下,来改善屏幕分辨率大小。

亚像素显示,是仅在液晶显示器上使用的一种增强字体清晰度的技术。但这种技术有时会出现问题,用投影仪投射到白色墙壁上,会出出字体显示不正常的情况,而且对于老式的 CRT 显示器是根本不支持的。